/*
 * Copyright (c) 2010 Google Inc.
 * 
 * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
 * in compliance with the License. You may obtain a copy of the License at
 * 
 * http://www.apache.org/licenses/LICENSE-2.0
 * 
 * Unless required by applicable law or agreed to in writing, software distributed under the License
 * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
 * or implied. See the License for the specific language governing permissions and limitations under
 * the License.
 */

package com.google.api.client.apache;

import org.apache.http.annotation.Immutable;
import org.apache.http.conn.ConnectTimeoutException;
import org.apache.http.conn.scheme.SchemeSocketFactory;
import org.apache.http.params.HttpConnectionParams;
import org.apache.http.params.HttpParams;

import java.io.IOException;
import java.net.InetSocketAddress;
import java.net.Proxy;
import java.net.Socket;
import java.net.SocketTimeoutException;

/**
 * The default class for creating plain (unencrypted) socks sockets.
 * <p>
 * The following parameters can be used to customize the behavior of this class:
 * <ul>
 * <li>{@link org.apache.http.params.CoreConnectionPNames#CONNECTION_TIMEOUT}</li>
 * <li>{@link org.apache.http.params.CoreConnectionPNames#SO_REUSEADDR}</li>
 * </ul>
 * 
 * @since 1.3
 * @author Valentin Haloiu
 */

@Immutable
public class PlainSocksSocketFactory implements SchemeSocketFactory {

  /**
   * Gets the default factory.
   * 
   * @return the default factory
   */
  public static PlainSocksSocketFactory getSocketFactory() {
    return new PlainSocksSocketFactory();
  }

  /**
   * @param params If the parameters contain values for socks.host and socks.port they are used as
   *        the socket server. If they are not set (or set to <code>null</code>) the function simply
   *        returns a new instance of {@link Socket} class using default constructor.
   * 
   * @return the new socket
   */
  public Socket createSocket(final HttpParams params) {
    Socket sock;
    if (params == null) {
      sock = new Socket();
    } else {
      String proxyHost = (String) params.getParameter("socks.host");
      Integer proxyPort = (Integer) params.getParameter("socks.port");
      if (proxyHost != null && proxyPort != null) {
        InetSocketAddress socksaddr = new InetSocketAddress(proxyHost, proxyPort);
        Proxy proxy = new Proxy(Proxy.Type.SOCKS, socksaddr);
        sock = new Socket(proxy);
      } else {
        sock = new Socket();
      }
    }
    return sock;
  }

  public Socket connectSocket(final Socket socket, final InetSocketAddress remoteAddress,
      final InetSocketAddress localAddress, final HttpParams params) throws IOException,
      ConnectTimeoutException {
    if (remoteAddress == null) {
      throw new IllegalArgumentException("Remote address may not be null");
    }
    if (params == null) {
      throw new IllegalArgumentException("HTTP parameters may not be null");
    }
    Socket sock = socket != null ? socket : createSocket(params);
    if (localAddress != null) {
      sock.setReuseAddress(HttpConnectionParams.getSoReuseaddr(params));
      sock.bind(localAddress);
    }
    int timeout = HttpConnectionParams.getConnectionTimeout(params);
    try {
      sock.connect(remoteAddress, timeout);
    } catch (SocketTimeoutException ex) {
      throw new ConnectTimeoutException("Connect to " + remoteAddress.getHostName() + "/"
          + remoteAddress.getAddress() + " timed out");
    }
    return sock;
  }

  /**
   * Checks whether a socket connection is secure. This factory creates plain socks socket
   * connections which are not considered secure.
   * 
   * @param sock the connected socket
   * 
   * @return <code>false</code>
   * 
   * @throws IllegalArgumentException if the argument is invalid
   */
  public final boolean isSecure(Socket sock) throws IllegalArgumentException {
    if (sock == null) {
      throw new IllegalArgumentException("Socket may not be null.");
    }
    // This check is performed last since it calls a method implemented
    // by the argument object. getClass() is final in java.lang.Object.
    if (sock.isClosed()) {
      throw new IllegalArgumentException("Socket is closed.");
    }
    return false;
  }

}
